/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi ImaginationLand.

    Maxsi ImaginationLand is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi ImaginationLand is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi ImaginationLand.  If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiImaginationLandContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiImaginationLandContributors.h. For more information please see
	MaxsiImaginationLandContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiImaginationLand.exe
	Imaginary programming!
	
	Main.cpp
	Basic WIN32 functions and stuff

****************************************************************************/

#include "MaxsiEngine.h"
#include <math.h>
#include <io.h>
#include <direct.h>
#include <iostream> 
#include <fcntl.h>
#include <SYS\Stat.h>
#include "Main.h"

#ifndef GOSH_WHY_DOESNT_THERE_EXIST_SOME_SIMPLE_GRAPH_SOFTWARE

#define ANIMATE
//#undef ANIMATE
#define WINDOW_X (4.0f)
#define WINDOW_Y (3.0f)
#ifdef ANIMATE
#define ANTIALIAS_QUALITY 4
unsigned int		TRIES_PER_PIXEL	=	1;
#else
#define ANTIALIAS_QUALITY 1
#define TRIES_PER_PIXEL 300
#endif

COLORREF*			Buffer1			=	NULL;
COLORREF*			Buffer2			=	NULL;
COLORREF*			FrontBuffer		=	NULL;
COLORREF*			BackBuffer		=	NULL;
DWORD				ZCalls			=	0;
DWORD_PTR			nThreads		=	0;
HANDLE				DrawEvent		=	NULL;
HANDLE				DrawedEvent		=	NULL;
HANDLE*				Thread			=	NULL;
HANDLE*				ThreadDone		=	NULL;
RECT				rectClient;

ME_WIN32ENTRYPOINT(MaxsiImaginationLand);

/****************************************************************************

    FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
    PURPOSE: calls initialization function, processes message loop

****************************************************************************/

int WINAPI MaxsiImaginationLand::WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
	//BenchMarkZ();
	//TestSlowInvSqrt();
	//TestFastInvSqrt();
		
	nThreads	=	GetNumberOfLogicalCPUs();
	
	return BaseClass::WinMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow);
}


/****************************************************************************

    FUNCTION: InitApplication(HANDLE)
    PURPOSE: Initializes window data and registers window class

****************************************************************************/

BOOL MaxsiImaginationLand::InitApplication(
    HANDLE hInstance)
{
    WNDCLASS  wc = {0};

    wc.lpfnWndProc = (WNDPROC) MaxsiImaginationLandProc;
    wc.hInstance = (HINSTANCE)hInstance;
    wc.hIcon = LoadIconA((HINSTANCE)hInstance, "ProductIcon");
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpszClassName = TEXT("MaxsiImaginationLand");

	if (!RegisterClass(&wc)) { return FALSE; }

	return TRUE;
}


/****************************************************************************

    FUNCTION:  InitInstance(HANDLE, int)
    PURPOSE:  Saves instance handle and creates main window

****************************************************************************/
BOOL MaxsiImaginationLand::InitInstance(
    HINSTANCE hInstance, 
    INT nCmdShow)
{
	nCmdShow	=	SW_SHOWMAXIMIZED;

    LPTSTR lpszText = NULL;
    BOOL bSuccess = TRUE;

    hInst = hInstance;

    hAccTable = LoadAccelerators(hInst, TEXT("MaxsiImaginationLand"));

	// Get the screen size
	DEVMODE Dev;
	BOOL	Result	=	EnumDisplaySettings(NULL,ENUM_CURRENT_SETTINGS,&Dev);
	int w = Dev.dmPelsWidth;
	int h = Dev.dmPelsHeight;
	int x = 0;
	int y = 0;

	char*	WindowTitle		=	BuildString(1,"Maxsi ImaginationLand");

    WindowHWND = CreateWindowA(
        "MaxsiImaginationLand",
        WindowTitle,
        (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX ),
        x, y,
        w, h,
        NULL, NULL, hInstance, NULL );

	delete[] WindowTitle;

	if (!WindowHWND) { return FALSE; }


	ME_Window*	Parent	=	ME_WindowGetPointer(WindowHWND);

	ShowWindow(WindowHWND, nCmdShow);
    bSuccess = UpdateWindow(WindowHWND);

	return bSuccess;
}

/****************************************************************************

    FUNCTION: MainWndProc(HWND, unsigned, WORD, LONG)
    PURPOSE:  Processes messages

****************************************************************************/

LRESULT APIENTRY MaxsiImaginationLand::WindowProc(
    HWND hWnd, 
    UINT message, 
    WPARAM wParam, 
    LPARAM lParam)
{
	switch (message) 
	{
			case WM_PAINT:
				Imagine();
				break;

		default:			
			return (BaseClass::WindowProc(hWnd, message, wParam, lParam));
	}
	return (0);

	return BaseClass::WindowProc(hWnd,message,wParam,lParam);
}


DWORD MaxsiImaginationLand::OnThreadRegistered(HWND hWnd)
{
	PostMessage(hWnd,WM_COMMAND,0,0);

	return BaseClass::OnThreadRegistered(hWnd);
}

/*__forceinline*/ ComplexFloat Z(ComplexFloat In, ComplexFloat C)
{/*
	In *= In;
	In += C;
	return In;
	*/
	return (In*In)+C;
}


/*__forceinline*/ void ZFast(ComplexFloat &z, ComplexFloat &C)
{ 
	float OldReal		=	z.Real;
	z.Real				*=	z.Real;
	z.Real				-=	z.Imaginary*z.Imaginary;
	z.Imaginary			*=	2.0f*OldReal;
	z.Imaginary			+=	C.Imaginary;
}


unsigned int	Iterations(ComplexFloat In)
{
	unsigned int Count = 0;
	bool HitOnce = false;
	ComplexFloat Cache(0,0);
	float	OldReal;
	ComplexFloat z(0,0);

	for ( Count = 0; Count < TRIES_PER_PIXEL; Count++ )
	{
		//z = Z(z,In);
		//ZFast(z,In);

		OldReal				=	z.Real;
		z.Real				*=	z.Real;
		z.Real				-=	z.Imaginary*z.Imaginary - In.Real;
		z.Imaginary			*=	2.0f*OldReal;
		z.Imaginary			+=	In.Imaginary;

		if ( Count % 4 == 0 )
		{
			if ( HitOnce )
			{
				if ( Cache == z )
				{
					ZCalls	+=	Count+1;
					return -1;
				}
			}
			else
			{
				HitOnce = true;
			}
			Cache	= z;
		}

		if ( z.Real > 2.0f || z.Real < -2.0f || z.Imaginary > 2.0f || z.Imaginary < -2.0f )
		{
			break;
		}
	}
	Count++;
	ZCalls	+=	Count+1;
	if ( Count == TRIES_PER_PIXEL+1 ) { Count = 0; }

	return Count;
}


DWORD_PTR WINAPI ImagineThread(LPVOID p)
{
	DWORD_PTR		ThreadId	=	(DWORD_PTR)p;
	ComplexFloat	In;
	unsigned int	Count;
	unsigned int	LastCount	=	-1;
	float			LastSQRTCount;	
	float	PixelW		=	WINDOW_X/rectClient.right;
	float	PixelH		=	WINDOW_Y/rectClient.bottom*-1.0f;
	float	AW			=	PixelW/ANTIALIAS_QUALITY;
	float	AH			=	PixelH/ANTIALIAS_QUALITY;
	float	PixelX		=	WINDOW_X/2.0f;
	float	PixelY		=	WINDOW_Y/2.0f;
	float	XOffset		=	0.0f;
	float	YOffset		=	0.0f;
	float	Colorness;
	unsigned int	iColorness;
	unsigned int	ColorR;
	unsigned int	ColorG;
	unsigned int	ColorB;

	while ( WaitForSingleObject(DrawEvent,INFINITE) == WAIT_OBJECT_0 )
	{
		float	sqrtTRIES_PER_PIXEL		=	(float)TRIES_PER_PIXEL;
		float	sqrtTRIES_PER_PIXELhalf	=	 0.5f*sqrtTRIES_PER_PIXEL;
		int i = *(int*)&sqrtTRIES_PER_PIXEL; // get bits for floating value
		i = 0x5f3759df - (i>>1); // gives initial guess y0
		sqrtTRIES_PER_PIXEL = *(float*)&i; // convert bits back to float
		sqrtTRIES_PER_PIXEL = sqrtTRIES_PER_PIXEL*(1.5f-sqrtTRIES_PER_PIXELhalf*sqrtTRIES_PER_PIXEL*sqrtTRIES_PER_PIXEL); // Newton step, repeating increases accuracy
		float	invsqrtTRIES_PER_PIXEL	=	sqrtTRIES_PER_PIXEL;

		for ( DWORD_PTR Y = ThreadId; Y < rectClient.bottom; Y+=nThreads )
		{
			for ( unsigned int X = 0; X < rectClient.right; X++ )
			{
				ColorR	=	0;
				ColorG	=	0;
				ColorB	=	0;

				XOffset	=	PixelW*X-PixelX;
				YOffset	=	PixelH*Y+PixelY;

				for ( unsigned int Ax = 0; Ax < (ANTIALIAS_QUALITY*ANTIALIAS_QUALITY); Ax++ )
				{
					In.Real			=	XOffset+(AW*(Ax%ANTIALIAS_QUALITY));
					In.Imaginary	=	YOffset+(AH*(Ax/ANTIALIAS_QUALITY));

					float	OldReal;
					ComplexFloat z(0,0);

					for ( Count = 0; Count < TRIES_PER_PIXEL; Count++ )
					{
						OldReal				=	z.Real;
						z.Real				*=	z.Real;
						z.Real				-=	z.Imaginary*z.Imaginary - In.Real;
						z.Imaginary			*=	2.0f*OldReal;
						z.Imaginary			+=	In.Imaginary;
							
						if ( 
							// Todo, we have some magic constants here, make them more 'precise'! :D
							(Count == 0 && z.Imaginary*z.Imaginary+z.Real*z.Real < 0.0625 ) ||
							(Count == 1 && z.Imaginary*z.Imaginary+z.Real*z.Real < 0.035226546f ) ||
							(Count == 2 && z.Imaginary*z.Imaginary+z.Real*z.Real < 0.0010055788f ) /*||
							(Count == 3 && z.Imaginary*z.Imaginary+z.Real*z.Real < 0.00019491167f ) ||
							(Count == 4 && z.Imaginary*z.Imaginary+z.Real*z.Real < 0.00021877403f )*/  )
						{
							//ZCalls	+=	Count+1;
							Count	=	-1;
							break;
						}
						
						if ( z.Real*z.Real+z.Imaginary+z.Imaginary > 4.0f )
						{
							break;
						}
					}

					if ( Count != -1 )
					{
						Count++;
						ZCalls	+=	Count+1;

						if ( Count == TRIES_PER_PIXEL+1 ) { Count = 0; }
					}

					if ( Count != -1 )
					{
						if ( LastCount == Count )
						{
							Colorness		=	LastSQRTCount*invsqrtTRIES_PER_PIXEL;
						}
						else
						{
							LastSQRTCount	=	sqrt((float)Count);
							Colorness		=	sqrt((float)Count)*LastSQRTCount;

							LastCount		=	Count;
						}

						iColorness		=	(unsigned int)128.0f*Colorness;
						
						ColorR			+=	min((unsigned int)(4*iColorness),255);
						ColorG			+=	min((unsigned int)(2*iColorness),255);
						ColorB			+=	min((unsigned int)(1*iColorness),255);
					}
				}

				ColorR /= (ANTIALIAS_QUALITY*ANTIALIAS_QUALITY);
				ColorG /= (ANTIALIAS_QUALITY*ANTIALIAS_QUALITY);
				ColorB /= (ANTIALIAS_QUALITY*ANTIALIAS_QUALITY);

				BackBuffer[Y*rectClient.right+X] = RGB(ColorB,ColorG,ColorR);
			}
		}

		SetEvent(ThreadDone[ThreadId]);

		WaitForSingleObject(DrawedEvent,INFINITE);
	}

	return 0;
}


void Imagine()
{	
	HWND	hWnd	=	Application.WindowHWND;

    PAINTSTRUCT ps          = {0};
	HDC hDC = BeginPaint (hWnd, &ps);

    GetClientRect (hWnd, &rectClient);

	if ( Buffer1 == NULL ) { Buffer1 = new COLORREF[rectClient.bottom*rectClient.right]; }
	if ( Buffer2 == NULL ) { Buffer2 = new COLORREF[rectClient.bottom*rectClient.right]; }

	BackBuffer	=	Buffer1;
	FrontBuffer	=	Buffer2;

	DrawEvent	=	CreateEvent(NULL,TRUE,FALSE,NULL);
	DrawedEvent	=	CreateEvent(NULL,TRUE,FALSE,NULL);


	ThreadDone	=	new HANDLE[nThreads];
	Thread		=	new HANDLE[nThreads];

	for ( DWORD_PTR I = 0; I < nThreads; I++ )
	{
		ThreadDone[I]	=	CreateEvent(NULL,TRUE,FALSE,NULL);
		Thread[I]		=	CreateThread(NULL,NULL,ImagineThread,(LPVOID)I,0,0);
	}

	
	DWORD	Start		=	GetTickCount();
	DWORD	End			=	GetTickCount();

	ResetEvent(DrawedEvent);
	SetEvent(DrawEvent);

#ifdef ANIMATE
	while ( TRIES_PER_PIXEL <= 300 )
	{
#endif		

		WaitForMultipleObjects(nThreads,ThreadDone,TRUE,INFINITE);
	
		End			=	GetTickCount();

		ResetEvent(DrawEvent);
		
		double	FPS		=	1.0f/((End-Start)/1000.0f);

		int		leet	=	1337;

		for ( DWORD_PTR I = 0; I < nThreads; I++ )
		{
			ResetEvent(ThreadDone[I]);
		}

		COLORREF*	Temp		=	BackBuffer;
					BackBuffer	=	FrontBuffer;
					FrontBuffer	=	Temp;

		SetEvent(DrawedEvent);
		DWORD	Start		=	GetTickCount();

		ResetEvent(DrawedEvent);
		SetEvent(DrawEvent);


		HBITMAP		DrawBackBuffer	=	CreateCompatibleBitmap(hDC, rectClient.right, rectClient.bottom);

		BITMAPINFO	DrawBackBufferInfo;
		DrawBackBufferInfo.bmiHeader.biBitCount = 32;
		DrawBackBufferInfo.bmiHeader.biClrImportant = 0;
		DrawBackBufferInfo.bmiHeader.biClrUsed = 0;
		DrawBackBufferInfo.bmiHeader.biCompression = BI_RGB;
		DrawBackBufferInfo.bmiHeader.biHeight = 0-rectClient.bottom;
		DrawBackBufferInfo.bmiHeader.biPlanes = 1;
		DrawBackBufferInfo.bmiHeader.biSize = sizeof(DrawBackBufferInfo);
		DrawBackBufferInfo.bmiHeader.biSizeImage = BI_RGB;
		DrawBackBufferInfo.bmiHeader.biWidth = rectClient.right;
		DrawBackBufferInfo.bmiHeader.biXPelsPerMeter = 0;
		DrawBackBufferInfo.bmiHeader.biYPelsPerMeter = 0;

		SetDIBits(hDC,DrawBackBuffer,0,rectClient.bottom,FrontBuffer,&DrawBackBufferInfo, DIB_RGB_COLORS );

		BITMAP    Bitmap;

		HDC hDCBits = CreateCompatibleDC(hDC);
		GetObject(DrawBackBuffer, sizeof(BITMAP), (LPSTR)&Bitmap);
		SelectObject(hDCBits, DrawBackBuffer);
		BitBlt(hDC, 0, 0, rectClient.right, rectClient.bottom, hDCBits, 0, 0, SRCCOPY);
		DeleteDC(hDCBits);

		DeleteObject(BackBuffer);

#ifdef ANIMATE
		//if ( TRIES_PER_PIXEL < 8 )
		//{
		//	TRIES_PER_PIXEL++;
		//}
		//else if ( TRIES_PER_PIXEL < 15 )
		//{
		//	TRIES_PER_PIXEL+=2;
		//}
		//else if ( TRIES_PER_PIXEL < 20 )
		//{
		//	TRIES_PER_PIXEL+=3;
		//}
		//else if ( TRIES_PER_PIXEL < 35 )
		//{
		//	TRIES_PER_PIXEL+=4;
		//}
		//else if ( TRIES_PER_PIXEL < 50 )
		//{
		//	TRIES_PER_PIXEL+=5;
		//}
		//else if ( TRIES_PER_PIXEL < 75 )
		//{
		//	TRIES_PER_PIXEL+=10;
		//}
		//else
		//{
		//	TRIES_PER_PIXEL+=15;
		//}
		TRIES_PER_PIXEL++;
		if ( TRIES_PER_PIXEL > 300 ) { TRIES_PER_PIXEL == 300; }
	}
#endif
	
	for ( DWORD_PTR I = 0; I < nThreads; I++ )
	{
		CloseHandle(ThreadDone[I]);
	}

	delete[]	ThreadDone; ThreadDone = NULL;

	CloseHandle(DrawEvent);

    EndPaint (hWnd, &ps);
}

void BenchMarkZ()
{
	ComplexFloat C(0.0f,2.0f);
	ComplexFloat z(0.0f,0.0f);

	DWORD	Start	=	GetTickCount();

	float	OldReal;

	for ( size_t N = 0; N < 100000000; N++ )
	{
#if 0 //3.7 MHz
		z = Z(z,C);
#elif 0 // 4.7 MHz
		z = (z*z)+C;
#elif 0 // 7.4 MHz
		z *= z;
		z += C;
#elif 1 // 50 MHz

		//float	OldA	=	A;
		//float	OldC	=	C;

		//A		=	(A*C) - (B*D);
		//B		=	(OldA*D) + (B*OldC);

		// If A == C && B == D then:

		//float	OldA	=	A;

		//A		=	(A*A) - (B*B);
		//B		=	(OldA*B) + (B*OldA);


		//float	OldA	=	A;

		//A		*=	A;
		//A		-=	(B*B);
		//B		=	2*OldA*B;


		//float	OldA	=	A;

		//A		*=	A;
		//A		-=	(B*B);
		//B		*=	2*OldA;


		OldReal		=	z.Real;
		z.Real		*=	z.Real;
		z.Real		-=	z.Imaginary*z.Imaginary + C.Real;
		z.Imaginary	*=	2.0f*OldReal;
		z.Imaginary +=	C.Imaginary;
#endif
	}

	DWORD	Time	=	GetTickCount()-Start;

	double	Speed	=	100000000.0f/(Time/1000.0f);

	return;
}

void ZResearch()
{
	return;
}


void TestSlowInvSqrt()
{
	DWORD	Start	=	GetTickCount();

	float	sqrtTRIES_PER_PIXEL;

	for ( size_t N = 0; N < 100000000; N++ )
	{
		sqrtTRIES_PER_PIXEL		=	1.0f/sqrt((float)TRIES_PER_PIXEL);
	}

	DWORD	Time	=	GetTickCount()-Start;

	double	Speed	=	100000000.0f/(Time/1000.0f);

	return;
}


void TestFastInvSqrt()
{
	DWORD	Start	=	GetTickCount();


	float	sqrtTRIES_PER_PIXEL;
	float	sqrtTRIES_PER_PIXELhalf;
	int i;

	for ( size_t N = 0; N < 100000000; N++ )
	{
		sqrtTRIES_PER_PIXEL		=	(float)TRIES_PER_PIXEL;
		sqrtTRIES_PER_PIXELhalf	=	 0.5f*sqrtTRIES_PER_PIXEL;
		i = *(int*)&sqrtTRIES_PER_PIXEL; // get bits for floating value
		i = 0x5f3759df - (i>>1); // gives initial guess y0
		sqrtTRIES_PER_PIXEL = *(float*)&i; // convert bits back to float
		sqrtTRIES_PER_PIXEL = sqrtTRIES_PER_PIXEL*(1.5f-sqrtTRIES_PER_PIXELhalf*sqrtTRIES_PER_PIXEL*sqrtTRIES_PER_PIXEL); // Newton step, repeating increases accuracy
	}

	DWORD	Time	=	GetTickCount()-Start;

	double	Speed	=	100000000.0f/(Time/1000.0f);

	return;
}

#endif